#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time      :2021/11/10 9:56
# @Author    :cjw
import os
import time
from typing import List, Type, Union, Optional

from selenium.webdriver.common.keys import Keys
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.chrome.webdriver import WebDriver as ChromeWebdriver
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException, ElementClickInterceptedException

from use_custom_web.browser.chrome import Chrome
from use_custom_web.util.log import logging
from use_custom_web.util.file_operator import YmlOperator
from use_custom_web.setting import BY_RULES, WEBDRIVER_WAIT_TIMEOUT, COOKIE_YML, FEISHU_BASE_URL


class BasePage:
	"""页面基类，把页面重复的步骤抽离出来"""

	_locators = {}  # 元素定位器
	_base_url = None

	def __init__(self, base_driver: Optional[WebDriver] = None):
		"""
		初始化，定义base_driver参数，解决driver 重复实例化的问题
		:param base_driver: 初始浏览器对
		"""
		if not base_driver:
			# 获取命令行中传递过来的browser参数，获取浏览器启动类型
			browser = os.getenv('browser')

			# 默认浏览器类型是chrome
			if browser:
				if browser.lower() == 'firefox':
					pass
				elif browser.lower() == 'edge':
					pass
			else:
				self.driver: ChromeWebdriver = Chrome().browser

			# 打开企业飞书登录页
			if self._base_url:
				self.driver.get(self._base_url)

				# 每次打开首页，进行cookie数据写入
				if self._base_url == FEISHU_BASE_URL:
					# 读取cookies.yml文件中的cookie信息并写入
					cookies = YmlOperator(COOKIE_YML).data
					for cookie in cookies:
						self.driver.add_cookie(cookie)
					self.driver.refresh()
		else:
			self.driver = base_driver

	def __getattr__(self, name: str):
		"""
		当使用【实例.属性】获取属性值时，如果实例没有该属性，就会调用这个方法
		用于实例属性访问，获取locators元素定位器中的值。
		locators元素定位的值保存在yml文件中,以字典的形式保存，
		字典的键是定位元素的名称，值是定位表达式【查找方式，查找值，flag标志存在即为查找多个】

		yml文件中保存的格式：
		department_name: # 部门名称
		  - xpath
		  - //ul[@role="group"]//a
		  - 1 # 这个值判断是否查找多个元素
		:param name: 定位元素的名称
		:return:
		"""
		# 判断定位元素名称是否存在
		if name not in self._locators:
			logging.error(msg := f'元素定位器locators中无定位属性的键：{name}')
			raise ValueError(msg)

		# 获取定位值，并校验元素定位方式
		by, val, *flag = self._locators[name]
		if by not in BY_RULES:
			logging.error(msg := f'不支持该元素定位方式：{by}，定位内容：{val}')
			raise ValueError(msg)

		try:
			# flag变量如果存在值，表示需要查找多个元素
			logging.info(f'查找元素，定位方式：{by}, 定位内容：{val}')
			if flag:
				element = WebDriverWait(self.driver, WEBDRIVER_WAIT_TIMEOUT).until(
					lambda x: x.find_elements(by, val))
			else:
				element = WebDriverWait(self.driver, WEBDRIVER_WAIT_TIMEOUT).until(
					lambda x: x.find_element(by, val))
			return element
		except TimeoutException as exc:
			screen = getattr(exc, 'screen')
			stacktrace = getattr(exc, 'stacktrace', None)
			logging.error(msg := f'查找元素超时，元素定位方式：{by}, 定位内容：{val}\n')
			raise TimeoutException(msg, screen, stacktrace)

	def find(self, by: Type[Union[str, tuple]], val: Optional[str] = None,
	         timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> WebElement:
		"""
		查找单个可见元素:
		两种传入定位元素的方式(by和val分开传入，或者直接一元组传入)，提高代码的兼容性
		如果传入的是元祖,那就只有一个参数
		:param by: 定位方式css、id，或者(定位方式+定位内容)
		:param val: 定位内容
		:param timeout: 显示等待时间，默认是10秒
		:return:
		"""
		try:
			# 判断接收定位元素值的方式
			if val:
				if by not in BY_RULES:
					logging.error(msg := f'不支持该元素定位方式：{by}，定位内容: {val}')
					raise ValueError(msg)
				logging.info(f'查找单个元素，定位方式：{by}, 定位内容：{val}')
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_element_located((by, val)))
			else:
				if by[0] not in BY_RULES:
					logging.error(msg := f'不支持该元素定位方式：{by[0]}，定位内容: {by[1]}')
					raise ValueError(msg)
				logging.info(f'查找元素，定位方式：{by[0]}, 定位内容：{by[1]}')
				element = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_element_located(by))
			return element
		except TimeoutException as exc:
			screen = getattr(exc, 'screen')
			stacktrace = getattr(exc, 'stacktrace', None)
			if val:
				logging.error(msg := f'查找单个元素超时，元素定位方式：{by}, 定位内容：{val}\n')
				raise TimeoutException(msg, screen, stacktrace)
			else:
				logging.error(msg := f'查找单个元素超时，元素定位方式：{by[0]}, 定位内容：{by[1]}\n')
				raise TimeoutException(msg, screen, stacktrace)

	def finds(self, by: Type[Union[str, tuple]], val: Optional[str] = None,
	          timeout: int = WEBDRIVER_WAIT_TIMEOUT) -> List[WebElement]:
		"""
		查找单个可见元素:
		两种传入定位元素的方式(by和val分开传入，或者直接一元组传入)，提高代码的兼容性
		如果传入的是元祖,那就只有一个参数
		:param by: 定位方式css、id，或者(定位方式+定位内容)
		:param val: 定位内容
		:param timeout: 显示等待时间，默认是10秒
		:return:
		"""
		try:
			# 判断接收定位元素值的方式
			if val:
				if by not in BY_RULES:
					logging.error(msg := f'不支持该元素定位方式：{by}，定位内容: {val}')
					raise ValueError(msg)
				logging.info(f'查找单个元素，定位方式：{by}, 定位内容：{val}')
				elements = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_all_elements_located((by, val)))
			else:
				if by[0] not in BY_RULES:
					logging.error(msg := f'不支持该元素定位方式：{by[0]}，定位内容: {by[1]}')
					raise ValueError(msg)
				logging.info(f'查找多个元素，定位方式：{by[0]}, 定位内容：{by[1]}')
				elements = WebDriverWait(self.driver, timeout).until(
					EC.visibility_of_all_elements_located(by))
			return elements
		except TimeoutException as exc:
			screen = getattr(exc, 'screen')
			stacktrace = getattr(exc, 'stacktrace', None)
			if val:
				logging.error(msg := f'查找多个元素超时，元素定位方式：{by}, 定位内容：{val}\n')
				raise TimeoutException(msg, screen, stacktrace)
			else:
				logging.error(msg := f'查找多个元素超时，元素定位方式：{by[0]}, 定位内容：{by[1]}\n')
				raise TimeoutException(msg, screen, stacktrace)

	def click(self, element: WebElement):
		"""
		按钮点击,点击的元素上面还有别的东西覆盖，将改成js点击
		:param element: 需要点击的元素
		:return:
		"""
		try:
			element.click()
		except ElementClickInterceptedException as exc:
			self.driver.execute_script('arguments[0].click();', element)

	def send_keys(self, element: WebElement, value, mode: str = None):
		"""
		先清空文本输入框原有值，再输入内容
		输入方式：
		:param element: 文本输入框
		:param value: 输入的值
		:param mode: 输入方式：js,模拟键盘(ctrl_a)输入，send_keys输入(默认)
		:return:
		"""
		if mode.lower() == 'ctrl_a':
			# 通过键盘全选，然后直接输入新的内容，就不用clear了
			element.send_keys(Keys.CONTROL, 'a')
			element.send_keys(value)
		elif mode.lower() == 'js':
			self.driver.execute_script(f'arguments[{element}].value = "";')
			self.driver.execute_script(f'arguments[{element}].value = {value};')
		else:
			element.click()
			element.clear()
			element.send_keys(value)
